import * as THREE from "three"
import { SetMouseCursorCommand, DisableCursorMeshCommand } from "../command/cursor.command"
import configConst from "../constant/config.const"
import CanvasData from "../data/CanvasData"
import EngineContext from "../EngineContext"
import cursorEnum from "../enum/cursor.enum"
import pinColorEnum from "../enum/pinColor.enum"
import { ClickEvent, HoverEvent, UnhoverEvent } from "../event/GestureEvent"
import { PinHeadCustomMaterial, PinHeadMaterial, PinStemMaterial } from "../material/PinMaterial"
import PinHeadMesh from "../mesh/PinHeadMesh"
import { PinClickedMessage, PinHoverChangeMessage } from "../message/pin.message"
import { matterItemData } from "../module/PinsModule"
import AnimatedNumber from "../object/AnimatedNumber"
import browserUtil from "../utils/browser.util"
import Comparator from "../utils/Comparator"
import { RenderLayer } from "../utils/RenderLayers"
import PinRenderer from "./PinRenderer"
function ce(
  e: {
    baseColor: THREE.Color
    hoverColor: THREE.Color
    dimmedColor: THREE.Color
  },
  t: string
) {
  switch (t) {
    case pinColorEnum.DEFAULT:
      return e.baseColor
    case pinColorEnum.DIMMED:
      return e.dimmedColor
    case pinColorEnum.HIGHLIGHTED:
      return e.hoverColor
  }
}
class PinStemMesh extends THREE.Line {
  vector: THREE.Vector3
  pinStemMaterial: PinStemMaterial
  constructor(e: THREE.Vector3, t: boolean, i: RenderLayer, s: PinHeadMesh, r: CanvasData) {
    const o = new THREE.BufferGeometry()
    const a = new Float32Array(6)
    a[0] = a[1] = a[2] = 0
    a[3] = e.x
    a[4] = e.y
    a[5] = e.z
    o.setAttribute("position", new THREE.BufferAttribute(a, 3))
    const l = new PinStemMaterial()
    super(o, l)
    this.geometry = o
    this.layers.mask = 5
    this.visible = t
    this.vector = e.clone()
    this.onBeforeRender = (e, t, i, n, o) => {
      const a = o
      a["uniforms"].pinHeadMatrix.value.copy(s.matrixWorld), a["uniforms"].resolution.value.set(r.width, r.height), (a["uniformsNeedUpdate"] = !0)
    }
    this.pinStemMaterial = l
  }
  dispose() {
    this.geometry.dispose()
  }
  updatePosition(e: matterItemData) {
    this.vector.copy(e.stemNormal).setLength(Math.max(e.stemLength, 0.01))
    const t = this.geometry.getAttribute("position")
    t.setXYZ(1, this.vector.x, this.vector.y, this.vector.z)
    t.needsUpdate = !0
  }
}
class PinMeshGroup extends THREE.Object3D {
  pinId: string
  pinType: string
  pinColor: {
    baseColor: THREE.Color
    hoverColor: THREE.Color
    dimmedColor: THREE.Color
  }
  stemVector: THREE.Vector3
  stemEnabled: boolean
  currentColorVariant: string
  baseOpacity: number
  static pinHeadGeometry: THREE.PlaneGeometry
  pinHeadMeshMaterial: PinHeadMaterial
  pinHeadMesh: PinHeadMesh
  stemMesh: PinStemMesh
  opacityAnimation: AnimatedNumber
  constructor(e: string, t: string, i: matterItemData, s, r: RenderLayer, o: CanvasData, a: THREE.Texture, l: THREE.Texture) {
    super()
    this.pinId = e
    this.pinType = t
    this.pinColor = s
    this.stemVector = new THREE.Vector3()
    this.stemEnabled = !0
    this.currentColorVariant = pinColorEnum.DEFAULT
    this.baseOpacity = 1
    PinMeshGroup.pinHeadGeometry || (PinMeshGroup.pinHeadGeometry = new THREE.PlaneBufferGeometry(configConst.BasePlaneSize, configConst.BasePlaneSize))
    const c = new THREE.Color().copy(s.baseColor)
    this.pinHeadMeshMaterial = new PinHeadMaterial(c, a, l, 1)
    this.pinHeadMesh = new PinHeadMesh(e, PinMeshGroup.pinHeadGeometry, this.pinHeadMeshMaterial, r)
    this.add(this.pinHeadMesh)
    this.stemMesh = new PinStemMesh(i.stemNormal, i.stemEnabled, r, this.pinHeadMesh, o)
    this.add(this.stemMesh)
    this.updateMeshPosition(i)
    this.opacityAnimation = new AnimatedNumber(1)
  }
  dispose() {
    this.remove(this.pinHeadMesh)
    this.pinHeadMesh.material.dispose()
    this.pinHeadMesh.dispose()
    this.remove(this.stemMesh)
    this.stemMesh.dispose()
  }
  static disposeAll() {
    PinMeshGroup.pinHeadGeometry.dispose()
  }
  updateFromPin(e: matterItemData, t: any, i, n) {
    this.position.copy(e.anchorPosition)
    this.pinHeadMesh.updatePosition(e)
    this.stemVector.copy(e.stemNormal).setLength(e.stemLength)
    this.stemEnabled = e.stemEnabled
    this.stemMesh.updatePosition(e)
    this.stemMesh.visible = e.stemEnabled
    t !== this.pinColor && ((this.pinColor = t), this.setColorVariant(this.currentColorVariant))
    const s = this.pinHeadMeshMaterial.uniforms
    ;(s.bg.value === i && s.mask.value === n) || ((s.bg.value = i), (s.mask.value = n), (this.pinHeadMeshMaterial.uniformsNeedUpdate = !0))
  }
  setColorVariant(e: string) {
    const t = ce(this.pinColor, e)
    this.pinHeadMeshMaterial.uniforms.color.value.copy(t)
    this.currentColorVariant = e
  }
  setOpacity(e: number) {
    this.baseOpacity = e
    this.updateOpacity()
  }
  fadeOpacity(e: number) {
    e > 0 && (this.visible = !0)
    this.opacityAnimation.modifyAnimation(this.opacityAnimation.value, e, 300).onComplete(() => {
      e <= 0 && (this.visible = !1)
    })
  }
  setVisibility(e, t) {
    this.visible = e
    this.pinHeadMesh.visible = e
    this.stemMesh.visible = e && t
  }
  setStemEnabled(e) {
    this.stemMesh.visible = this.visible && e
  }
  updateMeshPosition(e: matterItemData) {
    this.position.copy(e.anchorPosition)
    this.pinHeadMesh.updatePosition(e)
    this.stemMesh.updatePosition(e)
  }
  update(e: number, t: THREE.PerspectiveCamera, i: number, n: CanvasData) {
    this.pinHeadMesh.update(t, i, n)
    const s = this.opacityAnimation.active
    this.opacityAnimation.tick(e), s && this.updateOpacity()
  }
  setRenderOverrides(e, t) {
    this.pinHeadMesh.material = e ? new PinHeadCustomMaterial(e, !1) : this.pinHeadMeshMaterial
    t ? this.pinHeadMesh.geomScale.copy(t) : this.pinHeadMesh.geomScale.set(1, 1, 1)
    this.setOpacity(this.pinHeadMeshMaterial.opacity)
  }
  updateOpacity() {
    const e = this.opacityAnimation.value * this.baseOpacity
    if (((this.pinHeadMeshMaterial.opacity = e), (this.pinHeadMeshMaterial.uniforms.alpha.value = e), this.pinHeadMesh.material !== this.pinHeadMeshMaterial)) {
      this.pinHeadMesh.material.opacity = e
      const t = this.pinHeadMesh.material
      t && t.uniforms && t.uniforms.alpha && (t.uniforms.alpha.value = e)
    }
    this.stemMesh.pinStemMaterial.uniforms.alpha.value = e
  }
}
export default class NonInstancedPinRenderer extends PinRenderer {
  camera: THREE.PerspectiveCamera
  canvasData: CanvasData
  idToMesh: Map<string, PinMeshGroup>
  constructor(e, t, i, n, s, r) {
    super(e, n, s, r)
    this.camera = t
    this.canvasData = i
    this.idToMesh = new Map()
  }
  dispose() {
    super.dispose()
    this.removePinsByPredicate(() => !0)
    PinMeshGroup.disposeAll()
  }
  activate(e: EngineContext) {
    if (!this.bindings.length) {
      super.activate(e)
      this.bindings.push(
        this.input.registerPriorityHandler(ClickEvent, PinHeadMesh, (t, i) => {
          const n = i.parent
          return e.broadcast(new PinClickedMessage(n.pinId, n.pinType)), !0
        })
      )
      if (!browserUtil.canTouch()) {
        this.bindings.push(
          this.input.registerMeshHandler(HoverEvent, Comparator.isType(PinHeadMesh), (t, i) => {
            const n = i.parent
            e.broadcast(new PinHoverChangeMessage(n.pinId, !0, n.pinType)),
              e.commandBinder.issueCommand(new SetMouseCursorCommand(cursorEnum.FINGER)),
              e.commandBinder.issueCommand(new DisableCursorMeshCommand(!0))
          }),
          this.input.registerMeshHandler(UnhoverEvent, Comparator.isType(PinHeadMesh), (t, i) => {
            const n = i.parent
            e.broadcast(new PinHoverChangeMessage(n.pinId, !1, n.pinType)),
              e.commandBinder.issueCommand(new SetMouseCursorCommand(null)),
              e.commandBinder.issueCommand(new DisableCursorMeshCommand(!1))
          })
        )
      }
    }
  }
  render(e) {
    const t = this.getViewportScale(this.canvasData)
    this.idToMesh.forEach(i => {
      i.update(e, this.camera, t, this.canvasData)
    })
    super.render(e)
  }
  updatePin(e: string, t: string, i: matterItemData, n?: THREE.Texture, s?: THREE.Texture, r?) {
    super.updatePin(e, t, i, n, s, r)
    const l = this.getColor(i.color)
    let c = this.idToMesh.get(e)
    c || ((c = new PinMeshGroup(e, t, i, l, this.layer, this.canvasData, n, s)), this.idToMesh.set(e, c), this.input.registerMesh(c.pinHeadMesh, !1))
    !!r && (c.visible = r)
    c.updateFromPin(i, l, n, s)
    const h = i.floorId
    if (c.parent?.userData.floorId !== h) {
      c.parent?.remove(c)
      this.getFloorContainer(h).userData.typeContainers[t].add(c)
    }
  }
  removePin(e: string) {
    super.removePin(e)
    const i = this.idToMesh.get(e)
    i && (this.idToMesh.delete(e), i.parent?.remove(i), this.input.unregisterMesh(i.pinHeadMesh), i.dispose())
  }
  removePinsByPredicate(e) {
    this.idToMesh.forEach((t, i) => {
      e(t.pinType) && this.removePin(i)
    })
  }
  setPinVisible(e, t) {
    const i = this.idToMesh.get(e)
    i && (i.visible = t)
  }
  setPinColorVariant(e, t) {
    const i = this.idToMesh.get(e)
    i && i.setColorVariant(t)
  }
  setPinColorVariants(e, t?) {
    this.idToMesh.forEach(i => {
      i.pinId !== t && i.setColorVariant(e)
    })
  }
  setPinColorVariantByType(e, t, i) {
    this.idToMesh.forEach(n => {
      n.pinType === e && n.pinId !== i && n.setColorVariant(t)
    })
  }
  setPinOpacity(e, t) {
    const i = this.idToMesh.get(e)
    i && i.setOpacity(t)
  }
  fadePinOpacity(e, t) {
    const i = this.idToMesh.get(e)
    i && i.fadeOpacity(t)
  }
  setPinOpacityByType(e, t, i?) {
    this.idToMesh.forEach(n => {
      n.pinType === e && n.pinId !== i && n.setOpacity(t)
    })
  }
  fadePinOpacityByType(e, t, i = []) {
    this.idToMesh.forEach(n => {
      n.pinType !== e || i.includes(n.pinId) || n.fadeOpacity(t)
    })
  }
  setPinRenderOverrides(e, t, i) {
    super.setPinRenderOverrides(e, t, i)
    const n = this.idToMesh.get(e)
    n && n.setRenderOverrides(t, i)
  }
  pinHeadTransform(e: string) {
    const t = this.idToMesh.get(e)
    return t ? t.pinHeadMesh.matrixWorld : new THREE.Matrix4()
  }
}
